<?php
namespace Libsignal\protocol;

use Libsignal\ecc\ECPublicKey;
use Libsignal\util\ByteUtil;
use Libsignal\ecc\Curve;
use Libsignal\IdentityKey;
use Libsignal\exceptions\InvalidMessageException;
use Libsignal\exceptions\LegacyMessageException;
use Libsignal\exceptions\InvalidVersionException;
use Libsignal\exceptions\InvalidKeyException;
use Whispertext\KeyExchangeMessage as Textsecure_KeyExchangeMessage;

class KeyExchangeMessage
{
    const INITIATE_FLAG = 0x01;
    const RESPONSE_FLAG = 0x02;
    const SIMULTANEOUS_INITIATE_FLAG = 0x04;
    protected $version;
    protected $supportedVersion;
    protected $sequence;
    protected $flags;
    protected $baseKey;
    protected $baseKeySignature;
    protected $ratchetKey;
    protected $identityKey;
    protected $serialized;

    /**
     * KeyExchangeMessage constructor.
     * @param int $messageVersion
     * @param int $sequence
     * @param int $flags
     * @param ECPublicKey $baseKey
     * @param string $baseKeySignature
     * @param ECPublicKey $ratchetKey
     * @param IdentityKey $identityKey
     * @param string $serialized
     * @throws InvalidMessageException
     * @throws InvalidVersionException
     * @throws LegacyMessageException
     * @throws \Exception
     */
    public function __construct($messageVersion = null, $sequence = null, $flags = null,
                                        $baseKey = null, $baseKeySignature = null,
                                        $ratchetKey = null,
                                        $identityKey = null,
                                        $serialized = null){
        if ($serialized == null) {
            $this->supportedVersion = CiphertextMessage::CURRENT_VERSION;
            $this->version = $messageVersion;
            $this->sequence = $sequence;
            $this->flags = $flags;
            $this->baseKey = $baseKey;
            $this->baseKeySignature = $baseKeySignature;
            $this->ratchetKey = $ratchetKey;
            $this->identityKey = $identityKey;

            $version = ByteUtil::intsToByteHighAndLow($this->version, $this->supportedVersion);
            $keyExchangeMessage = new Textsecure_KeyExchangeMessage;
            $keyExchangeMessage->setId(($this->sequence << 5) | $this->flags);
            $keyExchangeMessage->setBaseKey($baseKey->serialize());
            $keyExchangeMessage->setRatchetKey($ratchetKey->serialize());
            $keyExchangeMessage->setIdentityKey($identityKey->serialize());

            if ($messageVersion >= 3) {
                $keyExchangeMessage->setBaseKeySignature($baseKeySignature);
            }

            $this->serialized = ByteUtil::combine([chr((int) $version), $keyExchangeMessage->serializeToString()]);
        } else {
            try {
                $parts = ByteUtil::split($serialized, 1, strlen($serialized) - 1);
                $this->version = ByteUtil::highBitsToInt(ord($parts[0][0]));
                $this->supportedVersion = ByteUtil::lowBitsToInt(ord($parts[0][0]));
                if ($this->version <= CiphertextMessage::UNSUPPORTED_VERSION) {
                    throw new LegacyMessageException('Unsupported legacy version: '.$this->version);
                }
                if ($this->version > CiphertextMessage::CURRENT_VERSION) {
                    throw new InvalidVersionException('Unkown version: '.$this->version);
                }
                $message = new Textsecure_KeyExchangeMessage();
                $message->mergeFromString($parts[1]);

                if ($message->getId() == null || $message->getBaseKey() == null ||
                   $message->getRatchetKey() == null || $message->getIdentityKey() == null ||
                    ($this->version >= 3 && $message->getBaseKeySignature() == null)) {
                    throw new InvalidMessageException('Some required fields are missing!');
                }

                $this->sequence = $message->getId() >> 5;
                $this->flags = $message->getId() & 0x1f;
                $this->serialized = $serialized;
                $this->baseKey = Curve::decodePoint($message->getBaseKey(), 0);
                $this->baseKeySignature = $message->getBaseKeySignature();
                $this->ratchetKey = Curve::decodePoint($message->getRatchetKey(), 0);
                $this->identityKey = new IdentityKey($message->getIdentityKey(), 0);
            } catch (InvalidKeyException $ex) {
                throw new InvalidMessageException($ex->getMessage());
            }
        }
    }

    public function getVersion()
    {
        return $this->version;
    }

    public function getBaseKey()
    {
        return $this->baseKey;
    }

    public function getBaseKeySignature()
    {
        return $this->baseKeySignature;
    }

    public function getRatchetKey()
    {
        return $this->ratchetKey;
    }

    public function getIdentityKey()
    {
        return $this->identityKey;
    }

    public function hasIdentityKey()
    {
        return true;
    }

    public function getMaxVersion()
    {
        return $this->supportedVersion;
    }

    public function isResponse()
    {
        return ($this->flags & self::RESPONSE_FLAG) != 0;
    }

    public function isInitiate()
    {
        return ($this->flags & self::INITIATE_FLAG) != 0;
    }

    public function isResponseForSimultaneousInitiate()
    {
        return ($this->flags & self::SIMULTANEOUS_INITIATE_FLAG) != 0;
    }

    public function getFlags()
    {
        return $this->flags;
    }

    public function getSequence()
    {
        return $this->sequence;
    }

    public function serialize()
    {
        return $this->serialized;
    }
}
